﻿
using System.Diagnostics;
using System.Text;

// 用法：可选传入根目录参数；默认使用应用当前目录
// 示例：dotnet run -- "D:\Repos"

var rootPath = args.Length > 0 ? args[0] : AppDomain.CurrentDomain.BaseDirectory;
Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;
#if DEBUG
rootPath = @"D:\我的代码";
#endif

Console.WriteLine($"扫描根目录：{rootPath}");
var rootDir = new DirectoryInfo(rootPath);
var subDirs = rootDir.GetDirectories();

// 解析操作类型（支持命令行传入 pull/push/build；若未指定则交互询问）
string? actionArg = null;
foreach (var a in args)
{
    if (string.Equals(a, "pull", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "push", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "build", StringComparison.OrdinalIgnoreCase))
    {
        actionArg = a.ToLowerInvariant();
        break;
    }
}
string action = actionArg ?? AskAction(); // "pull" / "push" / "build"

int totalRepos = 0;
int successCount = 0;
int failCount = 0;
var failedDirs = new List<string>();
var successDirs = new List<string>();

void PrintSection(string title, ConsoleColor color)
{
    Console.ForegroundColor = color;
    Console.WriteLine($"{title}");
    Console.ResetColor();
}

string AskAction()
{
    while (true)
    {
        Console.WriteLine("请选择操作：1) 拉取(pull)  2) 推送(push)  3) 编译(build)，输入 1/2/3 或 pull/push/build：");
        var input = Console.ReadLine()?.Trim().ToLowerInvariant();
        if (string.IsNullOrEmpty(input)) continue;
        if (input == "1" || input == "pull") return "pull";
        if (input == "2" || input == "push") return "push";
        if (input == "3" || input == "build") return "build";
        Console.WriteLine("输入无效，请重新选择。");
    }
}

foreach (var dir in subDirs)
{
    var gitDir = Path.Combine(dir.FullName, ".git");
    if (!Directory.Exists(gitDir))
    {
        continue; // 不是 git 仓库，跳过
    }

    totalRepos++;
    var actionLabelStart = action == "pull" ? "拉取" : action == "push" ? "推送" : "编译";
    PrintSection($"开始{actionLabelStart}：{dir.FullName}", ConsoleColor.Cyan);
    string? beforeHash = null;
    if (action == "pull")
    {
        beforeHash = GetHeadHash(dir.FullName);
    }

    int exitCode;
    List<string> output;
    List<string> errors;
    if (action == "pull" || action == "push")
    {
        (exitCode, output, errors) = RunGitCommand(dir.FullName, action);
        if (exitCode != 0 && ContainsDubiousOwnership(errors))
        {
            var safePath = dir.FullName.Replace('\\', '/');
            PrintSection($"检测到所有权问题，添加 safe.directory：{safePath}", ConsoleColor.Yellow);
            var addResult = AddSafeDirectory(safePath);
            if (addResult.ExitCode == 0)
            {
                PrintSection("已添加 safe.directory，重试操作", ConsoleColor.Yellow);
                beforeHash = GetHeadHash(dir.FullName);
                (exitCode, output, errors) = RunGitCommand(dir.FullName, action);
            }
            else
            {
                PrintSection($"添加 safe.directory 失败：退出码 {addResult.ExitCode}", ConsoleColor.Red);
            }
        }
    }
    else
    {
        (exitCode, output, errors) = RunDotnetBuild(dir.FullName);
    }
    if (exitCode != 0 && action == "pull" && ContainsNoTrackingInfo(output.Concat(errors)))
    {
        var branch = GetCurrentBranch(dir.FullName);
        if (string.IsNullOrWhiteSpace(branch) || string.Equals(branch, "HEAD", StringComparison.OrdinalIgnoreCase))
        {
            PrintSection("当前为分离HEAD或未知分支，无法自动设置上游，跳过", ConsoleColor.Yellow);
        }
        else if (!HasRemoteOrigin(dir.FullName))
        {
            PrintSection("未发现远端 origin，无法自动拉取，跳过", ConsoleColor.Yellow);
        }
        else
        {
            PrintSection($"未设置跟踪分支，尝试从 origin/{branch} 拉取并建立跟踪", ConsoleColor.Yellow);
            beforeHash = GetHeadHash(dir.FullName);
            var pullRes = RunGitCommand(dir.FullName, $"pull origin {branch}");
            (exitCode, output, errors) = pullRes;
            if (exitCode == 0)
            {
                var setUpRes = RunGitCommand(dir.FullName, $"branch --set-upstream-to=origin/{branch}");
                if (setUpRes.ExitCode == 0)
                {
                    PrintSection($"已为分支 {branch} 设置上游到 origin/{branch}", ConsoleColor.Green);
                }
                else
                {
                    PrintSection($"设置上游失败：退出码 {setUpRes.ExitCode}", ConsoleColor.Yellow);
                }
            }
        }
    }

    if (exitCode == 0)
    {
        successCount++;
        successDirs.Add(dir.FullName);
        var actionLabelDone = action == "pull" ? "拉取" : action == "push" ? "推送" : "编译";
        PrintSection($"{actionLabelDone}完成", ConsoleColor.Green);

        if (action == "pull")
        {
            var afterHash = GetHeadHash(dir.FullName);
            var updated = false;
            if (!string.IsNullOrWhiteSpace(beforeHash) && !string.IsNullOrWhiteSpace(afterHash))
            {
                updated = !string.Equals(beforeHash, afterHash, StringComparison.Ordinal);
            }
            else
            {
                updated = PullOutputIndicatesUpdate(output);
            }
            if (updated)
            {
                PrintSection("开始编译", ConsoleColor.Magenta);
                var buildResult = RunDotnetBuild(dir.FullName);
                if (buildResult.ExitCode == 0)
                {
                    PrintSection("编译完成", ConsoleColor.Green);
                }
                else
                {
                    PrintSection($"编译失败：退出码 {buildResult.ExitCode}", ConsoleColor.Red);
                }
            }
        }
    }
    else
    {
        failCount++;
        failedDirs.Add(dir.FullName);
        var actionLabelFail = action == "pull" ? "拉取" : action == "push" ? "推送" : "编译";
        PrintSection($"{actionLabelFail}失败：{dir.FullName}，退出码 {exitCode}", ConsoleColor.Red);
        if (errors.Count > 0)
        {
            Console.WriteLine("—— 错误摘要（最近几行）——");
            var brief = errors.Count > 5 ? errors.GetRange(errors.Count - 5, 5) : errors;
            foreach (var line in brief)
            {
                Console.WriteLine(line);
            }
            Console.WriteLine();
        }
    }

    Console.WriteLine();
}

PrintSection("========== 汇总 ==========", ConsoleColor.Yellow);
Console.WriteLine($"检测到仓库：{totalRepos}");
var actionLabelSummary = action == "pull" ? "拉取" : action == "push" ? "推送" : "编译";
Console.WriteLine($"{actionLabelSummary}成功：{successCount}");
Console.WriteLine($"{actionLabelSummary}失败：{failCount}");

if (totalRepos == 0)
{
    Console.WriteLine("当前目录下未发现包含 .git 的文件夹。");
}

//if (successDirs.Count > 0)
//{
//    Console.WriteLine($"成功{(action == "pull" ? "拉取" : "推送")}的目录列表：");
//    foreach (var d in successDirs)
//    {
//        Console.WriteLine($"- {d}");
//    }
//}

if (failCount > 0 && failedDirs.Count > 0)
{
    Console.WriteLine($"{(action == "pull" ? "拉取" : action == "push" ? "推送" : "编译")}失败或异常的目录列表：");
    foreach (var d in failedDirs)
    {
        Console.WriteLine($"{d}");
    }
}

Console.ReadLine();

// 在指定工作目录下执行 git 命令（pull/push），并实时打印输出与错误
(int ExitCode, List<string> Output, List<string> Errors) RunGitCommand(string workingDirectory, string gitCommand)
{
    var errors = new List<string>();
    var outputs = new List<string>();

    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "git",
            Arguments = gitCommand,
            WorkingDirectory = workingDirectory,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            StandardOutputEncoding = Console.OutputEncoding,
            StandardErrorEncoding = Console.OutputEncoding,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        using var proc = new Process { StartInfo = psi };

        proc.OutputDataReceived += (s, e) =>
        {
            if (e.Data != null)
            {
                Console.WriteLine(e.Data);
                outputs.Add(e.Data);
            }
        };
        proc.ErrorDataReceived += (s, e) =>
        {
            if (e.Data != null)
            {
                // 同步打印错误，同时记录用于摘要
                Console.WriteLine(e.Data);
                errors.Add(e.Data);
            }
        };

        proc.Start();
        proc.BeginOutputReadLine();
        proc.BeginErrorReadLine();
        proc.WaitForExit();

        return (proc.ExitCode, outputs, errors);
    }
    catch (Exception ex)
    {
        errors.Add(ex.Message);
        return (-1, outputs, errors);
    }
}

string? GetHeadHash(string workingDirectory)
{
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "git",
            Arguments = "rev-parse HEAD",
            WorkingDirectory = workingDirectory,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            StandardOutputEncoding = Console.OutputEncoding,
            StandardErrorEncoding = Console.OutputEncoding,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        using var proc = new Process { StartInfo = psi };
        proc.Start();
        var output = proc.StandardOutput.ReadToEnd();
        proc.WaitForExit();
        var line = output?.Trim();
        return string.IsNullOrWhiteSpace(line) ? null : line;
    }
    catch
    {
        return null;
    }
}

(int ExitCode, List<string> Output, List<string> Errors) RunDotnetBuild(string workingDirectory)
{
    var errors = new List<string>();
    var outputs = new List<string>();
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "dotnet",
            Arguments = "build --configuration Release",
            WorkingDirectory = workingDirectory,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            StandardOutputEncoding = Console.OutputEncoding,
            StandardErrorEncoding = Console.OutputEncoding,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        using var proc = new Process { StartInfo = psi };
        proc.OutputDataReceived += (s, e) =>
        {
            if (e.Data != null)
            {
                Console.WriteLine(e.Data);
                outputs.Add(e.Data);
            }
        };
        proc.ErrorDataReceived += (s, e) =>
        {
            if (e.Data != null)
            {
                Console.WriteLine(e.Data);
                errors.Add(e.Data);
            }
        };
        proc.Start();
        proc.BeginOutputReadLine();
        proc.BeginErrorReadLine();
        proc.WaitForExit();
        return (proc.ExitCode, outputs, errors);
    }
    catch (Exception ex)
    {
        errors.Add(ex.Message);
        return (-1, outputs, errors);
    }
}

bool ContainsDubiousOwnership(IEnumerable<string> lines)
{
    foreach (var l in lines)
    {
        if (l != null)
        {
            var s = l.ToLowerInvariant();
            if (s.Contains("dubious ownership") || s.Contains("safe.directory") || s.Contains("所有权") || s.Contains("可疑"))
            {
                return true;
            }
        }
    }
    return false;
}

(int ExitCode, List<string> Output, List<string> Errors) AddSafeDirectory(string repoPath)
{
    var errors = new List<string>();
    var outputs = new List<string>();
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "git",
            Arguments = $"config --global --add safe.directory \"{repoPath}\"",
            WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            StandardOutputEncoding = Console.OutputEncoding,
            StandardErrorEncoding = Console.OutputEncoding,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        using var proc = new Process { StartInfo = psi };
        proc.OutputDataReceived += (s, e) =>
        {
            if (e.Data != null)
            {
                Console.WriteLine(e.Data);
                outputs.Add(e.Data);
            }
        };
        proc.ErrorDataReceived += (s, e) =>
        {
            if (e.Data != null)
            {
                Console.WriteLine(e.Data);
                errors.Add(e.Data);
            }
        };
        proc.Start();
        proc.BeginOutputReadLine();
        proc.BeginErrorReadLine();
        proc.WaitForExit();
        return (proc.ExitCode, outputs, errors);
    }
    catch (Exception ex)
    {
        errors.Add(ex.Message);
        return (-1, outputs, errors);
    }
}

bool PullOutputIndicatesUpdate(IEnumerable<string> lines)
{
    foreach (var l in lines)
    {
        if (string.IsNullOrEmpty(l)) continue;
        var s = l.ToLowerInvariant();
        if (s.Contains("already up to date") || s.Contains("up to date") || s.Contains("已经是最新") || s.Contains("已是最新") || s.Contains("当前分支为最新"))
        {
            return false;
        }
        if (s.Contains("fast-forward") || s.Contains("updating ") || s.Contains("files changed") || s.Contains("insertions") || s.Contains("deletions") || s.Contains("快进") || s.Contains("更新") || s.Contains("更改") || s.Contains("改动"))
        {
            return true;
        }
    }
    return false;
}
bool ContainsNoTrackingInfo(IEnumerable<string> lines)
{
    foreach (var l in lines)
    {
        if (string.IsNullOrEmpty(l)) continue;
        var s = l.ToLowerInvariant();
        if (s.Contains("no tracking information") || s.Contains("set tracking information") || s.Contains("no tracking") || s.Contains("upstream") || s.Contains("未设置上游") || s.Contains("没有跟踪信息") || s.Contains("未设置跟踪"))
        {
            return true;
        }
    }
    return false;
}

string? GetCurrentBranch(string workingDirectory)
{
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "git",
            Arguments = "rev-parse --abbrev-ref HEAD",
            WorkingDirectory = workingDirectory,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            StandardOutputEncoding = Console.OutputEncoding,
            StandardErrorEncoding = Console.OutputEncoding,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        using var proc = new Process { StartInfo = psi };
        proc.Start();
        var output = proc.StandardOutput.ReadToEnd();
        proc.WaitForExit();
        var line = output?.Trim();
        return string.IsNullOrWhiteSpace(line) ? null : line;
    }
    catch
    {
        return null;
    }
}

bool HasRemoteOrigin(string workingDirectory)
{
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "git",
            Arguments = "remote",
            WorkingDirectory = workingDirectory,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            StandardOutputEncoding = Console.OutputEncoding,
            StandardErrorEncoding = Console.OutputEncoding,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        using var proc = new Process { StartInfo = psi };
        proc.Start();
        var output = proc.StandardOutput.ReadToEnd();
        proc.WaitForExit();
        var s = output?.ToLowerInvariant();
        return s != null && s.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Any(x => x.Trim() == "origin");
    }
    catch
    {
        return false;
    }
}